﻿using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger.Vhdl;

namespace Weazel.Badger
{
	public class ConversionContext
	{
		/// <summary>
		/// Reference to the gezel model being converted.
		/// </summary>
		private Gezel.Model.Model model;
		public Gezel.Model.Model Model
		{
			get { return model; }	
		}

		/// <summary>
		/// Name of the system to convert
		/// May be null
		/// </summary>
		private string systemToConvert;

		/// <summary>
		/// The configuration to use for this conversions.
		/// </summary>
		private Configuration configuration;
		public Configuration Configuration
		{
			get { return configuration; }
		}

		/// <summary>
		/// Map mapping names of datapaths to vhdl entities
		/// </summary>
		private Dictionary<string, ConversionEntity> datapaths = new Dictionary<string, ConversionEntity>();
		public Dictionary<string, ConversionEntity> Datapaths
		{
			get { return datapaths; }		
		}

		private Dictionary<string, ComponentDeclaration> ipBlocks = new Dictionary<string, ComponentDeclaration>();
		public Dictionary<string, ComponentDeclaration> IpBlocks
		{
			get { return ipBlocks; }		
		}

		/// <summary>
		/// Cloned datapaths has names different from their implementation. 
		/// This map maps the name of a cloned datapath to the name of its implementation.
		/// This map is initialized in the convert-controller method.
		/// </summary>
		private Dictionary<string, string> clonedDatapathToDatapathMap = new Dictionary<string, string>();
		public Dictionary<string, string> ClonedDatapathToDatapathMap
		{
			get { return clonedDatapathToDatapathMap; }
		}

    private List<DesignFile> sourceFiles = new List<DesignFile>();
    public List<DesignFile> SourceFiles
    {
      get { return sourceFiles; }
    }

    private List<Vhdl.LibraryFiles.Libraries> libraries = new List<Weazel.Badger.Vhdl.LibraryFiles.Libraries>();
    public List<Vhdl.LibraryFiles.Libraries> Libraries
    {
      get { return libraries; }
    }

    private DesignFile systemFile;
    public DesignFile SystemFile
    {
      get { return systemFile; }
    }

    public List<string> GetVhdlSourceFileNames()
    {
      List<string> list = new List<string>();
      foreach (DesignFile file in sourceFiles)        
        list.Add(file.FileName);
      return list;
    }


		/// <summary>
		/// Create a new conversion context object.
		/// </summary>
		/// <param name="model">Gezel model to be converted</param>
		public ConversionContext(Gezel.Model.Model model, Configuration configuration)
		{
			if (model == null)
				throw new NullReferenceException("Input 'model' cannot be null");

			if (configuration == null)
				throw new NullReferenceException("Input 'configuration' cannot be null");

			this.model = model;
			this.configuration = configuration;

      // should always be included ...
      libraries.Add(Vhdl.LibraryFiles.Libraries.StdLogicArithExt);
		}

		/// <summary>
		/// Convert a "system" targeted for simulation. 
		/// </summary>
		/// <param name="system">System object to convert</param>
		private DesignFile convertSystemForSimulation(Weazel.Gezel.Model.system system)
		{
			SimulationSystemEntity sys = new SimulationSystemEntity(this, model, system);
			sys.Convert();

			DesignFile file = new DesignFile(system.LocalName);

			// add imports
			file.DesignUnit.Add("library ieee;");
			file.DesignUnit.Add("use IEEE.std_logic_1164.all;");
			file.DesignUnit.Add("use IEEE.std_logic_arith.all;");
			file.DesignUnit.Add("library work;");

			file.AddEntity(sys);

			return file;
		}

		/// <summary>
		/// Convert a "system" targeted for synthesis
		/// </summary>
		/// <param name="system">System object to convert</param>
		private DesignFile convertSystemForSynthesis(Weazel.Gezel.Model.system system)
		{
			SynthesisSystemEntity sys = new SynthesisSystemEntity(this, model, system);
			
      sys.Convert();

			DesignFile file = new DesignFile(system.LocalName);

			// add imports
			file.DesignUnit.Add("library ieee;");
			file.DesignUnit.Add("use IEEE.std_logic_1164.all;");
			file.DesignUnit.Add("use IEEE.std_logic_arith.all;");
			file.DesignUnit.Add("library work;");

			file.AddEntity(sys);

			return file;
		}

		/// <summary>
		/// Converts a controller to corresponding VHDL code. The result is stored
		/// in the list of design files as well as in the list of datapaths.
		/// </summary>
		/// <param name="controller">Controller to convert</param>
		private void convertController(Weazel.Gezel.Model.Controller controller)
		{
			if (controller == null)
				throw new NullReferenceException("ConvertController(): controller is null");

			if (controller.Datapath == null)
				throw new NullReferenceException(string.Format("ConvertController(): No datapath associated with controller '{0}'", controller.Name));

			if (controller.Datapath.ClonedFromDatapath != null)
			{
				// add to map of cloned datapaths
				clonedDatapathToDatapathMap.Add(controller.Datapath.LocalName, controller.Datapath.ClonedFromDatapath);
			}
			// Only non-cloned datapaths should be converted (avoid duplication)
			if (controller.Datapath.ClonedFromDatapath == null)
			{
				ConversionEntity ctrl = null;

				switch (controller.Type)
				{
					case Weazel.Gezel.Model.Controller.ControllerType.Fsm:
						ctrl = new FiniteStateMachineEntity(this, controller);
						break;
					case Weazel.Gezel.Model.Controller.ControllerType.Hardwired:
						ctrl = new HardwiredEntity(this, controller);
						break;
					case Weazel.Gezel.Model.Controller.ControllerType.Sequencer:
						throw new NotSupportedException();
				}

				ctrl.Convert();

				// add to list of converted datapaths
				datapaths.Add(controller.Datapath.LocalName, ctrl);

				Vhdl.DesignFile file = new Weazel.Badger.Vhdl.DesignFile(controller.Datapath.LocalName);

				// add imports
				file.DesignUnit.Add("library ieee;");
				file.DesignUnit.Add("use IEEE.std_logic_1164.all;");
				file.DesignUnit.Add("use IEEE.std_logic_arith.all;");
				file.DesignUnit.Add("use std.textio.all;");

				if(configuration.ConvertDisplayDirectives)
					file.DesignUnit.Add("use work.weazel_text_io.all;");
				file.DesignUnit.Add("library work;");
				file.DesignUnit.Add("use work.std_logic_arithext.all;");

				file.AddEntity(ctrl);

				// add to list of result files
				sourceFiles.Add(file);
			}
		}

		private void convertDatapath(string datapathName)
		{
			// check if this datapath (and controller) has already been processed
			if (datapaths.ContainsKey(datapathName))
				return;

			Weazel.Gezel.Model.Datapath datapath = 
				model.Datapaths[datapathName];

			// process any child datapaths (recursive call)
			foreach (string childDatapathName in datapath.ChildDatapathRefs.Keys)
				convertDatapath(childDatapathName);

			// find controller and convert
			Weazel.Gezel.Model.Controller controller =
				model.GetController(datapathName);

			convertController(controller);
		}

		private void convertIpBlocks()
		{
			foreach (Weazel.Gezel.Model.IPBlock ipBlock in model.IPBlocks.Values)
			{
				ComponentDeclaration ipBlockDeclaration = 
					new ComponentDeclaration(ipBlock.LocalName, null);

				foreach (Weazel.Gezel.Model.InPort port in ipBlock.InPorts.Values)
					ipBlockDeclaration.AddIn(port.LocalName, 
						port.Width > 1 ? (Vhdl.Types.Type)new Vhdl.Types.StdLogicVector(port.Width) : 
						(Vhdl.Types.Type) new Vhdl.Types.StdLogic(), port.DeclarationPosition);

				foreach (Weazel.Gezel.Model.OutPort port in ipBlock.OutPorts.Values)
					ipBlockDeclaration.AddOut(port.LocalName, 
						port.Width > 1 ? (Vhdl.Types.Type)new Vhdl.Types.StdLogicVector(port.Width) : 
						(Vhdl.Types.Type) new Vhdl.Types.StdLogic(), port.DeclarationPosition);

				ipBlocks.Add(ipBlock.LocalName, ipBlockDeclaration);
			}
		}

		public Dictionary<string, ConversionEntity> Convert()
		{
			return this.Convert(true);
		}

		/// <summary>
		/// Begin the conversion..
		/// </summary>
		/// <returns>true if success, false if error or warnings</returns>
    public Dictionary<string, ConversionEntity> Convert(bool doWrite)
    {
			// before we do anything, check that there is either only one system
			// in the design, or the "SystemToConvert" property on the configuration
			// object is pointing to a valid system in the design

			if (configuration.SystemToConvert != null)
			{
				if (!model.Systems.ContainsKey(configuration.SystemToConvert))
				{
					string message =
						string.Format("Unable to locate system '{0}' in design", configuration.SystemToConvert);

					throw new ErrorHandling.InvalidSystemException(message);
				}
				else
				{
					systemToConvert = configuration.SystemToConvert;
				}
			}
			else if (model.Systems.Count > 1)
			{
				string message =
					"Multiple systems in design. Please specify system to convert.";

				throw new ErrorHandling.InvalidSystemException(message);
			}
			else if (model.Systems.Count == 1)
			{
				// ugly, but necessary ..
				foreach (Weazel.Gezel.Model.system s in model.Systems.Values)
					systemToConvert = s.LocalName;
			}
			else
			{
				// zero systems is ok .. 
				systemToConvert = null;
			}
			
			convertIpBlocks();

      // convert controllers and datapaths
      foreach (Weazel.Gezel.Model.Controller controller in model.Controllers.Values)
      {
				if (controller == null)
					throw new NullReferenceException();

        convertController(controller);        			
      }

			// top level datapaths does not have a controller associated directly
			foreach (Gezel.Model.Datapath datapath in model.Datapaths.Values)
			{
				if (!this.datapaths.ContainsKey(datapath.LocalName))
				{
					// such a datapath must be a clone
					System.Diagnostics.Debug.Assert(datapath.ClonedFromDatapath != null);

					// add to list of converted datapaths
					this.datapaths.Add(datapath.LocalName, this.datapaths[datapath.ClonedFromDatapath]);
				}
			}

			// child datapahs cannot be added until all dp's has been converted.
			foreach (ConversionEntity entity in datapaths.Values)
				entity.SetupChildDatapaths();
                         
      // convert system
			if(systemToConvert != null)
			{
				Weazel.Gezel.Model.system system = model.Systems[systemToConvert];

				if(configuration.CreatePortsOnTopEntityForUnconnectedNetsInSystem)
					systemFile = convertSystemForSynthesis(system);
				else
					systemFile = convertSystemForSimulation(system);
			}

			// important
			resolve();

			// finally, write the result to disk
			if(doWrite)
				write(configuration.OutputFolder);

			return this.datapaths;
    }

		/// <summary>
		/// Write conversion result to disk.
		/// </summary>
		/// <param name="outputDirectory">location where to write result</param>
		private void write(string outputDirectory)
		{
			foreach (Vhdl.DesignFile design in sourceFiles)			
				design.Write(outputDirectory);

			if (systemFile != null)
				systemFile.Write(outputDirectory);


			// only output WeazelTextIO if needed
			if (configuration.ConvertDisplayDirectives)
				libraries.Add(Vhdl.LibraryFiles.Libraries.WeazelTextIo);

			// output library files
			foreach (Vhdl.LibraryFiles.Libraries lib in libraries)
				Vhdl.LibraryFiles.LibraryFiles.OuputLibraryFile(lib, outputDirectory);

			// finally, create an ISE projects (if needed)
			if (configuration.CreateXilinxIseProjectFile)
				CreateXilinxIseProjectFile(outputDirectory);
		}

		private void resolve()
		{
			foreach (Vhdl.DesignFile design in sourceFiles)
				design.Resolve();

			if (systemFile != null)
				systemFile.Resolve();
		}

		private void CreateXilinxIseProjectFile(string outputDirectory)
		{
			Xilinx.IseProject project = new Weazel.Badger.Xilinx.IseProject();
			project.ProjectName = "badger_output";
			project.DevicePackage = "ff668";
			project.Device = "xc4vlx25";
			project.DeviceSpeed = "-11";
			project.DeviceFamily = "virtex4";

			// add source files
			foreach (Vhdl.DesignFile file in sourceFiles)
			{
				string filename = outputDirectory == null ?
					file.FileName + ".vhd" :
					outputDirectory + "//" + file.FileName + ".vhd";

				project.AddSourceFile(filename);
			}

			// add library files
			foreach (Vhdl.LibraryFiles.Libraries lib in libraries)
				project.AddSourceFile(Vhdl.LibraryFiles.LibraryFiles.GetLibraryFileName(lib));

			// add system file
			if (systemFile != null)
				project.AddSourceFile(systemFile.FileName + ".vhd");

			string projectFileName = outputDirectory == null ?
				"badger_ise.npl" :
				outputDirectory + "//badger_ise.npl";
			
			project.WriteToFile(projectFileName);
		}
	}
}
